package com.atguigu.gmall.item.service.impl;

import com.alibaba.fastjson.JSON;
import com.atguigu.gmall.common.constant.RedisConst;
import com.atguigu.gmall.feign.search.SearchFeignClient;
import com.atguigu.gmall.starter.cache.aspect.annotation.MallCache;
import com.atguigu.gmall.feign.product.ProductSkuDetailFeignClient;
import com.atguigu.gmall.starter.cache.service.CacheService;
import com.atguigu.gmall.item.service.SkuDetailService;
import com.atguigu.gmall.product.entity.SkuImage;
import com.atguigu.gmall.product.entity.SkuInfo;
import com.atguigu.gmall.product.entity.SpuSaleAttr;
import com.atguigu.gmall.product.vo.CategoryTreeVo;
import com.atguigu.gmall.product.vo.SkuDetailVo;
import com.atguigu.gmall.product.vo.SkuDetailVo.CategoryViewDTO;
import lombok.extern.slf4j.Slf4j;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;

import java.math.BigDecimal;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantLock;

@Slf4j
@Service
public class SkuDetailServiceImpl implements SkuDetailService {

    @Autowired
    ProductSkuDetailFeignClient skuDetailFeignClient;
    //创建线程池
    @Autowired
    ThreadPoolExecutor coreExecutor;
    @Autowired
    StringRedisTemplate redisTemplate;
    @Autowired
    CacheService cacheService;
    @Autowired
    RedissonClient redissonClient;
    @Autowired
    SearchFeignClient searchFeignClient;


    //todo 引入AOP 业务只关注业务，增强逻辑由切面负责
    @MallCache(//cacheKey=RedisConst.SKU_DETAIL_CACHE+"#{#args[0]}",
            bitMapName = RedisConst.SKUID_BITMAP,
            bitMapKey ="#{#args[0]}",
            //lockKey = RedisConst.SKU_LOCK+"#{#args[0]}",
            ttl = 7,
            unit = TimeUnit.DAYS
    )
    @Override
    public SkuDetailVo getSkuDetailData(Long skuId) {
        return this.getDataFromRPC(skuId);
    }

    @Override
    public void incrHotScore(Long skuId) {
        //开启线程 异步调用
        CompletableFuture.runAsync(()->{

            //累计热度分
            Long increment = redisTemplate.opsForValue().increment("sku:hotScore:" + skuId);
            //远程调用 增加热度分，同步给ES service-item ==》 service-search
            if (increment%100 == 0){
                searchFeignClient.updateHotScore(skuId, increment);
            }

        }, coreExecutor);
    }

    //引入了redisson分布式锁
//    public SkuDetailVo getSkuDetailDataWithDistLock(Long skuId) throws Exception {
//        //1查缓存数据
//        SkuDetailVo cache = cacheService.getFromCache(skuId);
//        if (cache!=null){
//            log.info("命中缓存");
//            return cache;
//        }
//        log.info("缓存未命中");
//        //2如果缓存没有，位图查询 【防止随机值穿透攻击】
//        boolean contain = cacheService.mightContain(skuId);
//        //如果缓存没有，位图 没有，返回null
//        if (!contain){
//            log.info("疑似攻击请求");
//            return null;
//        }
//
//        //加锁 【防止缓存击穿】
//        RLock lock = redissonClient.getLock(RedisConst.SKU_LOCK + skuId);
//
//        boolean tryLock = false;
//        try {
//            //加锁 tryLock 尝试竞争一下锁
//            tryLock = lock.tryLock();
//            //如果抢到锁了
//            if (tryLock){
//                log.info("抢到锁。。。");
//                //3如果缓存没有，位图 有，回源
//                cache = this.getDataFromRPC(skuId);
//                //4将查到的数据放入缓存中
//                cacheService.setData(skuId, cache);
//                return cache;
//            }else {
//                //没有抢到锁的请求，睡500毫秒，然后访问缓存
//                TimeUnit.MILLISECONDS.sleep(500);
//                cache = cacheService.getFromCache(skuId);
//                return cache;
//            }
//        } finally {
//            //解锁
//            if (tryLock){
//                try {
//                    log.info("解锁。。。");
//                    lock.unlock();
//                }catch (Exception e){}//吃掉异常
//            }
//        }
//    }



    //放在方法外面，new唯一一把锁：本地锁：在分布式场景中，会导致锁不住所有机器
    ReentrantLock lock = new ReentrantLock();//底层是CAS  AQS

    //引入本地缓存 map k=skuId v=SkuDetailVo
    private Map<Long,SkuDetailVo> cache = new ConcurrentHashMap<>();

    //引入位图 查询商品详情
//    public SkuDetailVo getSkuDetailDataWithLocalLock(Long skuId) {
//        //要返回的值
//        SkuDetailVo returnVal = null;
//
//        //1查缓存 K=  sku:info:49 == 商品详情json
//        log.info("开始查询商品详情");
//        returnVal = cacheService.getFromCache(skuId);
//        if (returnVal == null){//要判定出 真没(null) 和 假没(X)
//            //2位图 判断
//            boolean contain = cacheService.mightContain(skuId);
//            if (!contain){//位图没有
//                log.info("bitmap没有，疑似攻击请求，返回");
//                return null;
//            }
//            //位图里可能有
//            log.info("bitmap有，回源");
//
//            //拦截击穿风险  加锁
//            boolean tryLock = lock.tryLock();//尝试加锁，100W请求抢一个锁，
//            if (tryLock){//枪锁成功
//                log.info("加锁成功，正在回源");
//                //3缓存里没有：回源
//                returnVal = getDataFromRPC(skuId);
//                //4保存到缓存中,null值缓存可能不会被调用
//                cacheService.setData(skuId,returnVal);
//                lock.unlock();//解锁
//            }else {
//                //抢锁失败 ,500毫秒后从缓存中查询
//                try {
//                    log.info("加锁失败，稍等返回缓存数据");
//                    TimeUnit.MILLISECONDS.sleep(500);
//                    returnVal = cacheService.getFromCache(skuId);//不要递归 getSkuDetailData
//                } catch (InterruptedException e) {
//                }
//            }
//        }
//        return returnVal;
//    }

    //引入redis
    public SkuDetailVo getSkuDetailDataNullSave(Long skuId) {
        //1查缓存 K=  sku:info:49 == 商品详情json
        String json = redisTemplate.opsForValue().get("sku:info:" + skuId);
        //2缓存中 没有数据，存入 X
        if (StringUtils.isEmpty(json)){
            SkuDetailVo result = this.getDataFromRPC(skuId);

            //3放入缓存，即使 result是 null ，也存到redis
            String jsonDate = "X";
            if (result!=null){
                jsonDate = JSON.toJSONString(result);
            }

            redisTemplate.opsForValue().set("skuInfo:" + skuId, jsonDate,7, TimeUnit.DAYS);
            return result;
        }

        //4缓存中  真有:json真数据  假有:X
        if ("X".equals(json)){
            log.info("疑似攻击请求");
            return null;
        }

        //缓存中有值，json转对象
        SkuDetailVo result = JSON.parseObject(json,SkuDetailVo.class);
        return result;
    }

    //本地缓存
    public SkuDetailVo getSkuDetailDataLocalCahe(Long skuId) {

        //1先查缓存
        SkuDetailVo result = cache.get(skuId);
        //2缓存中没有
        if (result==null){
            log.info("商品详情：缓存未命中，回源");
            //3回源： 回到数据源头进行查询
            result = this.getDataFromRPC(skuId);
            //4数据同步到缓存中
            cache.put(skuId, result);
        }else {
            log.info("商品详情：缓存命中");
        }
            return result;
    }

    //远程调用，获取商品详情数据
    private SkuDetailVo getDataFromRPC(Long skuId) {
        // 获取商品详情数据
        SkuDetailVo data = new SkuDetailVo();

        // 异步编排
        //1、异步: 商品详情【图片】
        CompletableFuture<SkuInfo> skuInfoFuture = CompletableFuture.supplyAsync(() -> {
            log.info("商品详情:skuInfo");
            SkuInfo skuInfo = skuDetailFeignClient.getSkuInfo(skuId).getData();
            data.setSkuInfo(skuInfo);
            return skuInfo;
        },coreExecutor);

        //2、异步：图片
        CompletableFuture<Void> imageFuture = skuInfoFuture.thenAcceptAsync((skuInfo) -> {
            if (skuInfo==null) return;
            log.info("图片:skuImages");
            List<SkuImage> skuImages = skuDetailFeignClient.getSkuImages(skuId).getData();
            skuInfo.setSkuImageList(skuImages);
            data.setSkuInfo(skuInfo);
        }, coreExecutor);


        //3、异步:当前商品精确完整分类信息；
        CompletableFuture<Void> categoryFuture = skuInfoFuture.thenAcceptAsync((skuInfo) -> {
            if (skuInfo==null) return;
            log.info("分类信息:viewDTO");
            CategoryTreeVo categoryTreeVo = skuDetailFeignClient.getCategoryTreeWithC3Id(skuInfo.getCategory3Id()).getData();
            CategoryViewDTO viewDTO = this.convertToCategoryViewDTO(categoryTreeVo);//数据模型转换
            data.setCategoryView(viewDTO);
        }, coreExecutor);

        //4、异步:实时价格
        CompletableFuture<Void> priceFuture = skuInfoFuture.runAsync(() -> {
            log.info("实时价格:price");
            try {
                BigDecimal price = skuDetailFeignClient.getPrice(skuId).getData();
                data.setPrice(price);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }, coreExecutor);

        //5、异步:销售属性;
        CompletableFuture<Void> spuSaleAttrsFuture = skuInfoFuture.thenAcceptAsync((skuInfo) -> {
            if (skuInfo==null) return;
            log.info("销售属性:spuSaleAttrs");
            List<SpuSaleAttr> spuSaleAttrs = skuDetailFeignClient.getSpuSaleAttr(skuInfo.getSpuId(), skuId).getData();
            data.setSpuSaleAttrList(spuSaleAttrs);
        }, coreExecutor);

        //6、异步:当前sku的所有兄弟们的所有组合可能性。
        CompletableFuture<Void> valuesSkuJsonFuture = skuInfoFuture.thenAcceptAsync((skuInfo) -> {
            if (skuInfo==null) return;
            log.info("组合可能性:json");
            String json = skuDetailFeignClient.getValuesSkuJson(skuInfo.getSpuId()).getData();
            data.setValuesSkuJson(json);
        }, coreExecutor);

        //等待所有任务完成
        CompletableFuture
                .allOf(imageFuture,categoryFuture,priceFuture,spuSaleAttrsFuture,valuesSkuJsonFuture)
                .join();
        return data;
    }

    //数据模型转换
    private SkuDetailVo.CategoryViewDTO convertToCategoryViewDTO(CategoryTreeVo categoryTreeVo){
        CategoryViewDTO viewDTO = new CategoryViewDTO();

        viewDTO.setCategory1Id(categoryTreeVo.getCategoryId());
        viewDTO.setCategory1Name(categoryTreeVo.getCategoryName());

        CategoryTreeVo child1 = categoryTreeVo.getCategoryChild().get(0);
        viewDTO.setCategory2Id(child1.getCategoryId());
        viewDTO.setCategory2Name(child1.getCategoryName());

        CategoryTreeVo child2 = categoryTreeVo.getCategoryChild().get(0);
        viewDTO.setCategory3Id(child2.getCategoryId());
        viewDTO.setCategory3Name(child2.getCategoryName());
        return viewDTO;
    }
}
